/*
 *  Class name: PersonManagerImpl.java
 *  Version: 1.0
 *  Date: 3.6.2014
 */
package cz.muni.fi.pv168.addressbook.daos.impl;

import cz.muni.fi.pv168.addressbook.daos.PersonManager;
import cz.muni.fi.pv168.addressbook.entities.Person;
import cz.muni.fi.pv168.addressbook.entities.validators.PersonValidator;
import cz.muni.fi.pv168.addressbook.entities.validators.Validator;
import cz.muni.fi.pv168.addressbook.entities.xml.IDGenerator;
import cz.muni.fi.pv168.addressbook.entities.xml.XMLReaderWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;

/**
 *
 * @author Michal Ďuriš
 */
public class PersonManagerImpl implements PersonManager {

    private List<Person> persons;

    private PersonValidator validator;

    private XMLReaderWriter<Person> personXMLReaderWriter;

    private IDGenerator idGenerator;

    private PersonManagerImpl() {
        persons = new ArrayList<>();
    }

    private void init() throws JAXBException, IOException {

        validator = new PersonValidator();

        personXMLReaderWriter = new XMLReaderWriter<>("persons.xml");
        personXMLReaderWriter.init();

        getAllPersons();

        idGenerator = new IDGenerator();
        idGenerator.setInitialValue(persons, Person::getId);
    }

    private void orderPersonList() {
        Collections.sort(persons, Comparator.comparing(Person::getLastName));
    }

    private void checkPersonList() throws RuntimeException {

        if (!Validator.NOT_EMPTY_LIST_PREDICATE.test(persons)) {
            throw new RuntimeException();
        }
    }

    public static PersonManager getInstance() throws Exception {

        final PersonManagerImpl manager = new PersonManagerImpl();
        manager.init();

        return manager;
    }

    @Override
    public void createPerson(Person person) {

        if (persons == null) {
            throw new RuntimeException();
        }

        validator.validate(person, Validator.Test.REQUIRED);

        if (!Validator.CORRECT_ID_PREDICATE.test(person.getId())) {

            person.setId(idGenerator.getUniqueID());
        }

        persons.add(person);

        try {
            personXMLReaderWriter.store(persons);

        } catch (JAXBException | IOException ex) {
            persons.remove(person);
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void updatePerson(Person person) {

        checkPersonList();

        validator.validate(person, Validator.Test.ID, Validator.Test.REQUIRED);

        int previousPersonIndex = 0;
        Person previousPerson = new Person();
        Person currentPerson;

        for (int i = 0; i < persons.size(); i++) {

            currentPerson = persons.get(i);

            if (currentPerson.getId().equals(person.getId())) {

                previousPersonIndex = i;
                previousPerson.setPerson(currentPerson);

                currentPerson.setPerson(person);
            }
        }

        try {

            personXMLReaderWriter.store(persons);

        } catch (JAXBException | IOException ex) {

            persons.get(previousPersonIndex).setPerson(previousPerson);

            throw new RuntimeException(ex);
        }
    }

    @Override
    public void deletePerson(Person person) {

        checkPersonList();

        validator.validate(person, Validator.Test.ID, Validator.Test.REQUIRED);

        persons.remove(person);

        try {

            personXMLReaderWriter.store(persons);

        } catch (JAXBException | IOException ex) {

            persons.add(person);
            orderPersonList();

            throw new RuntimeException(ex);
        }

    }

    @Override
    public List<Person> getAllPersons() {

        try {

            persons = personXMLReaderWriter.load();
            orderPersonList();

        } catch (JAXBException ex) {
            throw new RuntimeException(ex);
        }

        return persons;
    }

    @Override
    public Person getPersonById(Long id) {

        if (!Validator.NOT_EMPTY_LIST_PREDICATE.test(persons)) {

            return null;
        }

        if (!Validator.CORRECT_ID_PREDICATE.test(id)) {
            throw new IllegalArgumentException();
        }

        for (Person person : persons) {
            if (person.getId().equals(id)) {
                return person;
            }
        }

        return persons.stream().filter((person) -> person.getId().equals(id))
                .findFirst().orElse(null);
    }

    @Override
    public List<Person> findPerson(String searchString) {

        if (!Validator.NOT_EMPTY_LIST_PREDICATE.test(persons)) {

            return Collections.emptyList();
        }

        List<Person> resultList = persons.stream().filter((person) -> (person
                                                                       .getLastName()
                                                                       .toLowerCase()
                                                                       .startsWith(
                                                                       searchString
                                                                       .toLowerCase())))
                .collect(Collectors.toCollection(ArrayList::new));

        if (Validator.NOT_EMPTY_LIST_PREDICATE.test(resultList)) {

            Collections.sort(resultList, Comparator.comparing(
                    Person::getLastName));

            return resultList;

        } else {

            return Collections.emptyList();
        }
    }

}
